[PATCH 3/4] dnp3: set a bound on the number of points per message
authorJason Ish <jason.ish@oisf.net>
Wed, 7 Jan 2026 15:23:09 +0000 (09:23 -0600)
committerAndreas Dolp <dev@andreas-dolp.de>
Sun, 22 Feb 2026 12:28:52 +0000 (13:28 +0100)
16384 is used as the max, but a configuration parameter has been
provided. The reason for setting an upper bound is that bit flags can
create a memory amplification as we parse them into individual data
structures.

Ticket: #8181
(cherry picked from commit 3a32bb5743c35afb3278a6448f7e9669512dbe92)

Origin: upstream, https://github.com/OISF/suricata/commit/fdd79bdb14488244604729f1d68ca4bc60000dbd.patch
Bug: https://redmine.openinfosecfoundation.org/issues/8181
Subject: Upstream fix for CVE-2026-22259 part 3

Gbp-Pq: Name CVE-2026-22259_3.patch

doc/userguide/upgrade.rst
rules/dnp3-events.rules
src/app-layer-dnp3.c
src/app-layer-dnp3.h
suricata.yaml.in

index ed4e59c7b1450b454fee2098353f6396a59be234..6c9903002b1312d5825475498e7e158e3e5b6216 100644 (file)
@@ -39,10 +39,11 @@ Upgrading to 7.0.14 (trixie-security 1:7.0.10-1~bpo13u3)
 
 Other Changes
 ~~~~~~~~~~~~~
-- ``dnp3`` has reduced the default maximum number of outstanding
-  transactions from 500 down to 32. A ``max-tx`` parameter has been
-  added to the ``dnp3`` parser for users that need a larger number of
-  in-flight transactions.
+- ``dnp3`` has reduced the maximum number of open transactions from
+  500 down to 32, and the maximum number of points per message from
+  unbounded to 16384. Configuration options, ``max-tx`` and
+  ``max-points`` have been added for users who may need to change
+  these defaults.
 
 Upgrading to 7.0.9
 ------------------
index e4890f884325bbbc76ee51a5a83ba8365197ee49..59e4c24d293821835879cd723e6897bcbcc2bb4f 100644 (file)
@@ -24,3 +24,8 @@ alert dnp3 any any -> any any (msg:"SURICATA DNP3 Bad transport CRC"; \
 # Unknown object.
 alert dnp3 any any -> any any (msg:"SURICATA DNP3 Unknown object"; \
       app-layer-event:dnp3.unknown_object; classtype:protocol-command-decode; sid:2270004; rev:2;)
+
+# Too many points in a message.
+alert dnp3 any any -> any any (msg:"SURICATA DNP3 Too many points in message"; \
+      app-layer-event:dnp3.too_many_points; \
+      classtype:protocol-command-decode; sid:2270005; rev:1;)
index 15de9bbe45ea5d0b4514e37bafe710611cbeef98..b1ad323eec238fdf9f47b79f380fbe7f828a4913 100644 (file)
@@ -98,15 +98,19 @@ enum {
  * attacks. */
 static uint64_t dnp3_max_tx = 32;
 
+/* The maximum number of points allowed per message (configurable). */
+static uint64_t max_points = 16384;
+
 /* Decoder event map. */
 SCEnumCharMap dnp3_decoder_event_table[] = {
-    {"FLOODED",           DNP3_DECODER_EVENT_FLOODED},
-    {"LEN_TOO_SMALL",     DNP3_DECODER_EVENT_LEN_TOO_SMALL},
-    {"BAD_LINK_CRC",      DNP3_DECODER_EVENT_BAD_LINK_CRC},
-    {"BAD_TRANSPORT_CRC", DNP3_DECODER_EVENT_BAD_TRANSPORT_CRC},
-    {"MALFORMED",         DNP3_DECODER_EVENT_MALFORMED},
-    {"UNKNOWN_OBJECT",    DNP3_DECODER_EVENT_UNKNOWN_OBJECT},
-    {NULL, -1},
+    { "FLOODED", DNP3_DECODER_EVENT_FLOODED },
+    { "LEN_TOO_SMALL", DNP3_DECODER_EVENT_LEN_TOO_SMALL },
+    { "BAD_LINK_CRC", DNP3_DECODER_EVENT_BAD_LINK_CRC },
+    { "BAD_TRANSPORT_CRC", DNP3_DECODER_EVENT_BAD_TRANSPORT_CRC },
+    { "MALFORMED", DNP3_DECODER_EVENT_MALFORMED },
+    { "UNKNOWN_OBJECT", DNP3_DECODER_EVENT_UNKNOWN_OBJECT },
+    { "TOO_MANY_POINTS", DNP3_DECODER_EVENT_TOO_MANY_POINTS },
+    { NULL, -1 },
 };
 
 /* Calculate the next transport sequence number. */
@@ -709,6 +713,7 @@ static int DNP3DecodeApplicationObjects(DNP3Transaction *tx, const uint8_t *buf,
     uint32_t len, DNP3ObjectList *objects)
 {
     int retval = 0;
+    uint64_t point_count = 0;
 
     if (buf == NULL || len == 0) {
         return 1;
@@ -839,6 +844,13 @@ static int DNP3DecodeApplicationObjects(DNP3Transaction *tx, const uint8_t *buf,
             goto next;
         }
 
+        /* Check if we've exceeded the maximum number of points per message. */
+        point_count += object->count;
+        if (point_count > max_points) {
+            DNP3SetEventTx(tx, DNP3_DECODER_EVENT_TOO_MANY_POINTS);
+            goto done;
+        }
+
         int event = DNP3DecodeObject(header->group, header->variation, &buf,
             &len, object->prefix_code, object->start, object->count,
             object->points);
@@ -1614,6 +1626,13 @@ void RegisterDNP3Parsers(void)
         if (ConfGetInt("app-layer.protocols.dnp3.max-tx", &value)) {
             dnp3_max_tx = (uint64_t)value;
         }
+
+        /* Parse max-points configuration. */
+        if (ConfGetInt("app-layer.protocols.dnp3.max-points", &value)) {
+            if (value > 0) {
+                max_points = (uint64_t)value;
+            }
+        }
     } else {
         SCLogConfig("Parser disabled for protocol %s. "
             "Protocol detection still on.", proto_name);
index aae07f9c8095118c9d6f924800ca9bb5d582b053..3e40c7ac662d99cd90fc82b098f9037bee40d4a2 100644 (file)
@@ -109,6 +109,7 @@ enum {
     DNP3_DECODER_EVENT_BAD_TRANSPORT_CRC,
     DNP3_DECODER_EVENT_MALFORMED,
     DNP3_DECODER_EVENT_UNKNOWN_OBJECT,
+    DNP3_DECODER_EVENT_TOO_MANY_POINTS,
 };
 
 /**
index 1b54e1f24769759bb603bb068954f7ebf3448fa4..1514c01a5fc4497332662793ba76c9c95da62584 100644 (file)
@@ -1162,6 +1162,7 @@ app-layer:
       detection-ports:
         dp: 20000
       #max-tx: 32
+      #max-points: 16384
 
     # SCADA EtherNet/IP and CIP protocol support
     enip: